/**
 * Copyright 2005 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.edi.util.dynacode;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 动态编译源代码
 * @author cya
 *
 */
public class DynaComplier {
	private static final Log log = LogFactory.getLog(DynaComplier.class);
	/**
	 * 编译时用到的classPath路径
	 */
	private String compileClasspath;
	/**
	 * 类加载器，当前线程
	 */
	private ClassLoader parentClassLoader;
	/**
	 * 源代码文件夹
	 */
	private ArrayList sourceDirs =new ArrayList();
	/**
	 * 已经被加载的类，防止重复加载，便于垃圾回收
	 */
	private HashMap loadedClasses =new HashMap();
	
	public DynaComplier(){
		this(Thread.currentThread().getContextClassLoader());
	}
	public DynaComplier(ClassLoader parentClassLoader) {
		this(getCompileClasspath(parentClassLoader), parentClassLoader);
	}
	public DynaComplier(String compileClasspath, ClassLoader parentClassLoader ) {
		this.compileClasspath = compileClasspath;
		this.parentClassLoader = parentClassLoader;
	}
	/**
	 * 获取编译java类时所需要的classpath
	 * @param cl 当前线程cl
	 * @return
	 */
	private static String getCompileClasspath(ClassLoader cl){
		StringBuffer buffer=new StringBuffer();
		while(cl!=null){
			if(cl instanceof URLClassLoader){
				URL[] urls=((URLClassLoader)cl).getURLs();
//				log.debug(" urls ="+urls.length);
				for(int i=0;i<urls.length;i++){
//					log.debug(" url ="+urls[i]);
					if(buffer.length()>0)
						buffer.append(File.pathSeparatorChar);
					buffer.append(urls[i].getFile().toString());
//					log.debug(" innner buf="+buffer.toString());
				} 
			}
			cl = cl.getParent();//不断寻找父加载器，直到为空
		}
//		log.debug(" getCompileClasspath ="+buffer.toString());
		return buffer.toString();
	}
	/**
	 * 添加源代码路径 成功，返回true,如果已经存在返回false
	 * @param srcDir
	 * @return
	 */
	public boolean addSourceDir(File srcDir,String extraJAR){
		try {
			//返回抽象路径，即使该路径不存在
			srcDir=srcDir.getCanonicalFile();
		} catch (IOException e) { 
			e.printStackTrace();
		}
		synchronized(this.sourceDirs){
			for(int i=0;i<sourceDirs.size();i++){
				SourceDir  src=(SourceDir)sourceDirs.get(i);
				if(src.srcDir.equals(srcDir))
					return false;
			}
		}
		SourceDir newDir=new SourceDir(srcDir,extraJAR);
		sourceDirs.add(newDir);
		log.info(" add "+srcDir.getAbsolutePath()+" success");
		return true;
		
	}
	/**
	 * 根据类名称加载类
	 * @param className
	 * @return
	 * @throws ClassNotFoundException
	 */
	public Class loadClass(String className)throws ClassNotFoundException {
		LoadedClass loadedClass=null;
//		log.debug("className ="+className);
		synchronized(this.loadedClasses){
			loadedClass=(LoadedClass)loadedClasses.get(className);
		}
//		log.debug(" loadedClass ==null  "+(loadedClass==null));
		if(loadedClass==null){
			//不存在，编译java源文件
			String srcName=className.replace(".", "/")+".java";
//			log.debug(" srcName ="+srcName);
			//查找sourceDirs 是否已经存在
			SourceDir src=locateResource(srcName);
//			log.debug("src==null  "+(src.srcDir.getAbsolutePath()));
//			log.debug("src==null  "+(src.binDir.getAbsolutePath()));
			if(src==null){
				throw new ClassNotFoundException(" class not found "+ className);
			}
			synchronized (this){
				loadedClass=new LoadedClass(className,src);
			}
			synchronized (loadedClasses) {
				loadedClasses.put(className, loadedClass);
			}
			return loadedClass.clazz;
		}
		if (loadedClass.isChanged()) {
			// 先卸载再加载新类
			unload(loadedClass.srcDir);
			return loadClass(className);
		}

		return loadedClass.clazz;
		
		
	}
	/**
	 * 卸载类
	 * @param srcDir
	 */
	private void unload(SourceDir srcDir){
		synchronized(this.loadedClasses){
			Collection values=loadedClasses.values();
			Iterator it=values.iterator();
			while(it.hasNext()){
				LoadedClass loadedClass = (LoadedClass) it.next();
				if (loadedClass.srcDir == srcDir) {
					it.remove();
				} 
			}
		}
		
	}
	/**
	 * 根据类全路径到java源文件夹下加载java文件
	 * @param resource
	 * @return
	 */
	private SourceDir locateResource(String resource) {
		for (int i = 0; i < sourceDirs.size(); i++) {
			SourceDir src = (SourceDir) sourceDirs.get(i);
			if (new File(src.srcDir, resource).exists()) {
				return src;
			}
		}
		return null;
	}
	
	/**
	 * 根据源java文件夹，创建编译后的class文件夹，生成classLoader
	 * @author cya
	 *
	 */
	private class SourceDir{
		File srcDir ;
		File binDir ;
		Javac javac;
		URLClassLoader classLoader;
		public SourceDir(File srcDir,String extraJAR){
			this.srcDir=srcDir;
			String subdir = srcDir.getAbsolutePath().replace(':', '_').replace('/', '_').replace('\\', '_');	
//			String COMPILE_DIR=System.getProperty("COMPILE_DIR"); 
//			this.binDir = new File("d:/","dynaCode/" + subdir);//System.getProperty("java.io.tmpdir")
			this.binDir = srcDir;//new File(COMPILE_DIR+"../WEB-INF/workarea");//System.getProperty("java.io.tmpdir")
//			this.binDir.mkdirs();
			log.debug(" binDir ="+binDir.getAbsolutePath());
			//测试代码，在compileClasspath 后加上lib目录
			if(StringUtils.isNotBlank(extraJAR))
				compileClasspath=compileClasspath+";"+extraJAR;
			this.javac=new Javac(compileClasspath,binDir.getAbsolutePath());
			recreateClassLoader();
		} 
		void recreateClassLoader() {
			try {
				System.out.println("binDir.toURL ="+binDir.toURL());
				classLoader = new URLClassLoader(new URL[] { binDir.toURL() },
						parentClassLoader);
			} catch (MalformedURLException e) { 
				e.printStackTrace(); 
			}
		}
	}
	/**
	 * 根据类完整路径加载类，如果class文件不存在，就编译java文件生成class
	 * @author cya
	 *
	 */
	private class LoadedClass{
		String className;
		SourceDir srcDir;
		File srcFile;
		File binFile;
		Class clazz;
		long lastModified;
		LoadedClass(String className,SourceDir srcDir){
			this.className=className;
			this.srcDir=srcDir;
			String path=className.replace(".", "/");
			this.srcFile=new File(srcDir.srcDir,path+".java");
			this.binFile=new File(srcDir.binDir,path+".class");
//			log.debug(" this.srcFile ="+this.srcFile.getAbsolutePath());
//			log.debug(" this.binFile ="+this.binFile.getAbsolutePath());
			complieAndLoadClass();
		}
		void complieAndLoadClass(){
//			log.debug(" clazz!=null ="+(clazz!=null));
			if(clazz!=null)
				return;
			String error=null;
//			log.debug("binFile.lastModified()<srcFile.lastModified() ="+(binFile.lastModified()<srcFile.lastModified()));
			if(!binFile.exists()||(binFile.lastModified()<srcFile.lastModified())){
				error=srcDir.javac.compile(new File[]{srcFile});
			}
			if (error != null) {
				throw new RuntimeException("Failed to compile "+ srcFile.getAbsolutePath() + ". Error: " + error);
			}
			try{
				clazz=srcDir.classLoader.loadClass(className); 
				lastModified=srcFile.lastModified();
			}catch(ClassNotFoundException e){
				throw new RuntimeException("Failed to load  class "+ srcFile.getAbsolutePath());
			}
			log.info(" load class sucess "+ clazz);
			 
		}
		boolean isChanged() {
			return srcFile.lastModified() != lastModified;
		}
		
	}
	

}
